package com.arpa.wms.service;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.IterUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil;
import com.arpa.ntocc.common.common.exception.ServiceException;
import com.arpa.ntocc.common.common.util.UserUtil;
import com.arpa.wms.domain.consts.NumConst;
import com.arpa.wms.domain.dto.InventoryOperationDto;
import com.arpa.wms.domain.entity.DeliveryDistribution;
import com.arpa.wms.domain.entity.Inventory;
import com.arpa.wms.domain.entity.InventoryRecord;
import com.arpa.wms.domain.enums.InventoryJobStatusEnum;
import com.arpa.wms.domain.enums.InventoryOperationMethodEnum;
import com.arpa.wms.domain.enums.InventoryRecordTypeEnum;
import com.arpa.wms.mapper.InventoryMapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.extern.log4j.Log4j2;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import static java.util.stream.Collectors.toList;

/**
 * 库存操作类
 *
 * @author liuxr
 * @date 2021年04月22日 17:53
 */
@Service
@Log4j2
public class InventoryOperationService {

    @Resource
    private InventoryMapper inventoryMapper;


    @Resource
    private IInventoryRecordService iInventoryRecordService;

    /**
     * 库存操作
     *
     * @param inventory  库存信息，用于保存操作日志
     * @param sourceCode 源单号
     * @param opQuantity 操作数量
     * @param method     操作方法 +-
     * @param recordType 操作节点类型
     */
    @Transactional(rollbackFor = Exception.class)
    public void operation(final Inventory inventory,
                          final String sourceCode,
                          final BigDecimal opQuantity,
                          final InventoryOperationMethodEnum method,
                          final InventoryRecordTypeEnum recordType) {
        log.info("库存操作请求。源单号：{}；操作数量：{}；操作方法：{}；节点类型：{}；库存信息：{}", sourceCode, opQuantity, method, recordType, JSONUtil.toJsonStr(inventory));

        switch (method) {
            /**
             * 增加库存
             */
            case ADD: {
                //TODO 上架特殊处理
                //判断是否有相同库存
                //有：
                //      在已有库存增加库存；
                //      保存库存记录；、
                //无：
                //      新增库存；
                //      保存库存记录
                Inventory oldInventory = hasSameInventory(inventory.getLot()
                        , inventory.getGoodsCode()
                        , inventory.getLocation()
                        , inventory.getGoodsStatus(),
                        inventory.getIsLock()
                        , inventory.getContainerBarCode()
                        , inventory.getGmtStock());
                if (null != oldInventory) {
                    //更新增加老库存
                    this.addInventory(oldInventory.getCode(), inventory.getQuantity());
                    //TODO 增加库存变动记录
                } else {
                    this.insertInventory(inventory.getCode(), inventory.getQuantity());
                    //TODO 增加库存变动记录
                }

                break;
            }
            /**
             * 减少库存
             */
            case SUBTRACT: {
                this.subtractInventory(inventory.getCode(), opQuantity);
                //TODO 增加库存变动记录
                iInventoryRecordService.asynInsertRecord(inventory, sourceCode, opQuantity.negate(), recordType.getValue());
                break;
            }
            default:
        }
    }

    /**
     * 加库存操作
     *
     * @param inventory  库存信息，用于保存操作日志
     * @param sourceCode 源单号
     * @param opQuantity 操作数量
     * @param recordType 操作节点类型
     */
    @Transactional(rollbackFor = Exception.class)
    public void add(final Inventory inventory,
                    final String sourceCode,
                    final BigDecimal opQuantity,
                    final InventoryRecordTypeEnum recordType) {
        log.info("加库存操作请求。源单号：{}；操作数量：{}；节点类型：{}；库存信息：{}", sourceCode, opQuantity, recordType, JSONUtil.toJsonStr(inventory));
        if (opQuantity.compareTo(BigDecimal.ZERO) == 0) {
            log.info("加库存操作请求,操作数为0，不进行操作。");
            return;
        }
        this.addInventory(inventory.getCode(), opQuantity);
        iInventoryRecordService.asynInsertRecord(inventory, sourceCode, opQuantity, recordType.getValue());
    }

    /**
     * 减库存操作
     *
     * @param inventory  库存信息，用于保存操作日志
     * @param sourceCode 源单号
     * @param opQuantity 操作数量
     * @param recordType 操作节点类型
     */
    @Transactional(rollbackFor = Exception.class)
    public void subtract(final Inventory inventory,
                         final String sourceCode,
                         final BigDecimal opQuantity,
                         final InventoryRecordTypeEnum recordType) {
        log.info("减库存操作请求。源单号：{}；操作数量：{}；节点类型：{}；库存信息：{}", sourceCode, opQuantity, recordType, JSONUtil.toJsonStr(inventory));
        if (opQuantity.compareTo(BigDecimal.ZERO) == 0) {
            log.info("减库存操作请求,操作数为0，不进行操作。");
            return;
        }
        this.subtractInventory(inventory.getCode(), opQuantity);
        iInventoryRecordService.asynInsertRecord(inventory, sourceCode, opQuantity, recordType.getValue());
    }


    @Transactional(rollbackFor = Exception.class)
    public int subtractBatch(List<InventoryOperationDto> subtract) {
        log.info("批量减库存操作请求。操作信息：{}", JSONUtil.toJsonStr(subtract));
        return this.subtractInventoryBatch(subtract);
    }

    @Transactional(rollbackFor = Exception.class)
    public int subtractSingle(InventoryOperationDto subtract) {
        log.info("单条减库存操作请求。操作信息：{}", JSONUtil.toJsonStr(subtract));
        return this.subtractInventoryBatch(ListUtil.list(false, subtract));
    }

    @Transactional(rollbackFor = Exception.class)
    public int addBatch(List<InventoryOperationDto> add) {
        return this.addInventoryBatch(add);
    }

    @Transactional(rollbackFor = Exception.class)
    public int adjustBatch(List<InventoryOperationDto> adjust) {
        return this.adjustUsedQuantityBatch(adjust);
    }




    @Async
    public AsyncResult asyncSubtractSingle(InventoryOperationDto subtract) {
        int count = subtractSingle(subtract);
        if (count > 0) {
            return new AsyncResult(true);
        }
        return new AsyncResult(false);
    }

    /**
     * 判断是否有相同库存
     *
     * @param lot              批次
     * @param goodsCode        商品code
     * @param location         库位
     * @param goodsStatus      库存状态
     * @param isLock           是否锁定
     * @param containerBarCode 容器
     * @param gmtStock         存货日期
     * @return com.arpa.wms.domain.entity.Inventory
     * @author liuxr
     * @date 2021/4/22 19:18
     */
    private Inventory hasSameInventory(String lot
            , String goodsCode
            , String location
            , String goodsStatus
            , Integer isLock
            , String containerBarCode
            , LocalDateTime gmtStock) {
        // 批次、库位、商品、入驻商（商品、货主绑定在一起，似乎可以省略）、库存状态、是否锁定、容器（待扩展）,
        return inventoryMapper.selectOne(new LambdaQueryWrapper<Inventory>()
                .eq(Inventory::getLot, lot)
                .eq(Inventory::getLocation, location)
                .eq(Inventory::getGoodsCode, goodsCode)
                .eq(Inventory::getGoodsStatus, goodsStatus)
                .isNull(ObjectUtil.isNull(gmtStock), Inventory::getGmtStock)
                .likeRight(ObjectUtil.isNotNull(gmtStock), Inventory::getGmtStock, ObjectUtil.isNotNull(gmtStock) ? gmtStock.toLocalDate() : "")
                .eq(Inventory::getIsLock, isLock)
                .eq(Inventory::getTempInventory, NumConst.NUM_ZERO)
                .isNull(ObjectUtil.isNull(containerBarCode), Inventory::getContainerBarCode)
                .eq(ObjectUtil.isNotNull(containerBarCode), Inventory::getContainerBarCode, containerBarCode)
                .gt(Inventory::getQuantity, BigDecimal.ZERO)
                .eq(Inventory::getJobStatus, InventoryJobStatusEnum.NORMAL.getValue()));
    }

    /**
     * 老库存增加库存
     *
     * @param inventoryCode 库存标识
     * @param quantity      增加数量
     * @author liuxr
     * @date 2021/4/22 20:23
     */
    private void addInventory(String inventoryCode, BigDecimal quantity) {
        int count = inventoryMapper.addInventory(inventoryCode, quantity);
        if (count == 0) {
            log.error("库存:{} 增加{}失败！", inventoryCode, quantity);
            throw new ServiceException("库存操作失败");
        }
        log.info("加库存操作。库存:{} 增加{}成功！", inventoryCode, quantity);
    }

    /**
     * 插入新库存
     *
     * @param inventoryCode 库存标识
     * @param quantity      增加数量
     * @author liuxr
     * @date 2021/4/22 20:24
     */
    private void insertInventory(String inventoryCode, BigDecimal quantity) {
        int count = inventoryMapper.insertInventory(inventoryCode, quantity);
        if (count == 0) {
            log.error("库存:{} 增加{}失败！", inventoryCode, quantity);
            throw new ServiceException("库存操作失败");
        }
        log.info("加库存操作。库存:{} 增加{}成功！", inventoryCode, quantity);
    }

    /**
     * 减少库存
     *
     * @param inventoryCode 库存标识
     * @param quantity      减少数量
     * @author liuxr
     * @date 2021/4/22 20:25
     */
    private void subtractInventory(String inventoryCode, BigDecimal quantity) {
        int count = inventoryMapper.subtractInventory(inventoryCode, quantity);
        if (count == 0) {
            log.error("库存:{} 减少{}失败！", inventoryCode, quantity);
            throw new ServiceException("库存操作失败");
        }
        log.info("减库存操作。库存:{} 减少{}成功！", inventoryCode, quantity);
    }

    /**
     * 批量减少库存
     *
     * @param subtract 操作库存
     * @author liuxr
     * @date 2021/4/22 20:25
     */
    private int subtractInventoryBatch(List<InventoryOperationDto> subtract) {
        int count = inventoryMapper.subtractInventoryBatch(subtract);
        return count;

    }

    private int addInventoryBatch(List<InventoryOperationDto> add) {
        int count = inventoryMapper.addInventoryBatch(add);
        return count;
    }

    private int adjustUsedQuantityBatch(List<InventoryOperationDto> adjust) {
        int count = inventoryMapper.adjustUsedQuantityBatch(adjust);
        return count;
    }
    /**
     * 波次拣货跳过符合出库更新库存
     *
     * @param deliveryDistributions 订单分配明细
     * @author liuxr
     * @date 2021/5/26 19:38
     */
    @Transactional(rollbackFor = Exception.class)
    public void autoUpdateInventory(List<DeliveryDistribution> deliveryDistributions) {
        if (IterUtil.isEmpty(deliveryDistributions)) {
            log.info("出库确认更新库存跳过，订单号为空；");
            return;
        }

        //通过分配明细获取所有临时库存标识
        Set<String> tempInventoryCodeSet = deliveryDistributions.parallelStream().map(DeliveryDistribution::getInventoryCode).collect(Collectors.toSet());
        log.info("波次拣货跳过符合出库更新库存,通过分配明细获取所有临时库存标识,{}",JSONUtil.toJsonStr(tempInventoryCodeSet));

        //通过临时库存、原始库存获取所有库存信息
        List<Inventory> inventories = inventoryMapper.selectList(new LambdaQueryWrapper<Inventory>().in(Inventory::getCode, tempInventoryCodeSet));
        log.info("波次拣货跳过符合出库更新库存,通过临时库存、原始库存获取所有库存信息,{}",JSONUtil.toJsonStr(inventories));

        //将库存信息通过库存code转成map
        Map<String, Inventory> inventoryMap = inventories.stream().collect(Collectors.toMap(Inventory::getCode, r -> r));

        List<InventoryOperationDto> subtract = deliveryDistributions.parallelStream().map(d -> {
            InventoryOperationDto dto = new InventoryOperationDto();
            dto.setUuid(IdUtil.simpleUUID());
            dto.setSourceCode(d.getDeliveryCode());
            dto.setQuantity(d.getDistributionQuantity());
            dto.setInventoryCode(d.getInventoryCode());
            dto.setInventory(inventoryMap.get(d.getInventoryCode()));
            return dto;
        }).collect(toList());

        List<InventoryRecord> inventoryRecords = new ArrayList<>();
        String groupCode = deliveryDistributions.get(NumConst.NUM_ZERO).getGroupCode();
        //创建减库存操作记录
        subtract.forEach(r -> {
            //临时库存减库存操作记录
            InventoryRecord record = new InventoryRecord();
            BeanUtil.copyProperties(r.getInventory(), record);
            record.setCode(IdUtil.simpleUUID());
            record.setGroupCode(groupCode);
            record.setInventoryCode(r.getInventoryCode());
            record.setSourceCode(r.getSourceCode());
            record.setNum(r.getQuantity().negate());
            record.setType(InventoryRecordTypeEnum.OUTBOUND.getValue());
            inventoryRecords.add(record);
        });

        //批量减少临时库存
        this.subtractBatch(subtract);

        //批量插入操作记录
        iInventoryRecordService.saveBatch(inventoryRecords);
    }
}

